use crate::pipe::*;
use std::collections::VecDeque;

#[derive(PartialEq, Debug)]
pub struct Area {
    pipe_array: Vec<Vec<Pipe>>,
    start_position: Position,
    row_size: usize,
    col_size: usize,
}

impl Area {
    pub fn new(pipe_array: Vec<Vec<Pipe>>, start_position: Position, row_size: usize, col_size: usize) -> Self {
        Self { pipe_array, start_position, row_size, col_size }
    }

    pub fn enclosed_pipe_size(&self) -> usize {
        self.pipe_array.iter().map(|pipes| {
            let mut enclosed = false;
            let mut is_north = true;
            let mut size: usize = 0;
            pipes.iter().for_each(|pipe| {
                match pipe {
                    Pipe::Ground => {
                        if enclosed {
                            size += 1;
                        }
                    }
                    Pipe::Horizontal => {}
                    Pipe::Vertical => enclosed = !enclosed,

                    Pipe::NorthEast => is_north = true,
                    Pipe::SouthEast => is_north = false,

                    Pipe::NorthWest => {
                        if !is_north {
                            enclosed = !enclosed;
                        }
                    }
                    Pipe::SouthWest => {
                        if is_north {
                            enclosed = !enclosed;
                        }
                    }
                }
            });
            size
        }).sum()
    }

    pub fn farthest_steps(&self) -> usize {
        let mut steps: usize = 0;
        let mut res: usize = 0;

        let mut steps_array: Vec<Vec<i64>> = vec![
            vec![-1; self.col_size as usize]; 
            self.row_size as usize
        ];
        steps_array[self.start_position.row as usize][self.start_position.col as usize] = 0;

        let position_valid = |position: &Position| -> bool {
            if position.row < 0 || position.col < 0 {
                return false;
            }
            if position.row >= self.row_size as i64 || position.col >= self.col_size as i64 {
                return false;
            }
            return true;
        };

        let mut q: VecDeque<Position> = VecDeque::new();
        q.push_back(self.start_position);

        while !q.is_empty() {
            steps += 1;
            let mut next_loop_positions: Vec<Position> = Vec::new();

            loop {
                if let Some(position) = q.pop_front() {
                    let pipe = self.pipe(&position);

                    let next_positions = pipe.next(&position);
                    for next_position in next_positions {
                        if !position_valid(&position) {
                            continue;
                        }
                        if steps_array[next_position.row as usize][next_position.col as usize].clone() != -1 {
                            continue;
                        }
                        res = steps;
                        steps_array[next_position.row as usize][next_position.col as usize] = steps as i64;
                        next_loop_positions.push(next_position);
                    }
                } else {
                    break;
                }
            }

            for position in next_loop_positions {
                q.push_back(position);
            }
        }

        res
    }

    fn pipe(&self, position: &Position) -> &Pipe {
        self.pipe_array.get(position.row as usize).unwrap().get(position.col as usize).unwrap()
    }
}

impl From<&str> for Area {
    fn from(value: &str) -> Self {
        let mut pipe_array = Vec::new();
        let mut start_position = Position::default();

        for (row, line) in value.lines().enumerate() {
            let mut pipe_line = Vec::new();
            for (col, token) in line.chars().enumerate() {
                if token == 'S' {
                    // Record position
                    start_position.row = row as i64;
                    start_position.col = col as i64;
                    pipe_line.push(make_pipe('|'));
                } else {
                    pipe_line.push(make_pipe(token));
                }
            }
            pipe_array.push(pipe_line);
        }

        let row_size = pipe_array.len();
        let col_size = pipe_array.first().unwrap().len();

        // Reset start position
        let mut north_connect = false;
        let mut south_connect = false;
        let mut east_connect = false;
        let mut west_connect = false;
        
        let pipe = |row: i64, col: i64| -> Pipe {
            return pipe_array[row as usize][col as usize];
        };

        if start_position.row > 0 {
            let north_pipe = pipe(start_position.row - 1, start_position.col);
            match north_pipe {
                Pipe::Vertical | Pipe::SouthEast | Pipe::SouthWest => north_connect = true,
                _ => (),
            }
        }

        if start_position.row < (row_size - 1) as i64 {
            let south_pipe = pipe(start_position.row + 1, start_position.col);
            match south_pipe {
                Pipe::Vertical | Pipe::NorthEast | Pipe::NorthWest => south_connect = true,
                _ => (),
            }
        }

        if start_position.col > 0 as i64 {
            let west_pip = pipe(start_position.row, start_position.col - 1);
            match west_pip {
                Pipe::Horizontal | Pipe::NorthEast | Pipe::SouthEast => west_connect = true,
                _ => (),
            }
        }

        if start_position.col < (col_size - 1) as i64 {
            let east_pipe = pipe(start_position.row, start_position.col + 1);
            match east_pipe {
                Pipe::Horizontal | Pipe::NorthWest | Pipe::SouthWest => east_connect = true,
                _ => (),
            }
        }

        let start_pipe = 
            pipe_array.get_mut(start_position.row as usize).unwrap().get_mut(start_position.col as usize).unwrap();
        match (north_connect, south_connect, west_connect, east_connect) {
            (true, true, false, false) => *start_pipe = Pipe::Vertical,
            (false, false, true, true) => *start_pipe = Pipe::Horizontal,
            (true, false, true, false) => *start_pipe = Pipe::NorthWest,
            (true, false, false, true) => *start_pipe = Pipe::NorthEast,
            (false, true, true, false) => *start_pipe = Pipe::SouthWest,
            (false, true, false, true) => *start_pipe = Pipe::SouthEast,
            _ => panic!("{}-{}-{}-{}", north_connect, south_connect, west_connect, east_connect),
        };

        Self { pipe_array, start_position, row_size, col_size }
    }
}